#pragma once

#include "RUdpSocket.h"

class ClientList
{
  static const int SESSION_EXPIRE_PERIOD_MS = 60000; // Set this to a low number just for testing

public:
  ClientList()
  {
    InitializeSRWLock(&mRwSessionsLock);
  }

  void AddSession(RUdpSocket* session)
  {
    session->AddRef();
    session->SetSessionList(this);

    AcquireSRWLockExclusive(&mRwSessionsLock);
    mSessions.insert(std::make_pair(session->GetHash(), session));
    ReleaseSRWLockExclusive(&mRwSessionsLock);
  }

  RUdpSocket* FindSession(UINT64 hash)
  {
    RUdpSocket* session = 0;

    AcquireSRWLockShared(&mRwSessionsLock);

    SessionMap::iterator it = mSessions.find(hash);

    // Found an existing session!
    if (it != mSessions.end())
    {
      session = it->second;
      session->AddRef();
    }

    ReleaseSRWLockShared(&mRwSessionsLock);

    return session;
  }

  void RemoveStaleSession(RUdpSocket* session)
  {
    AcquireSRWLockExclusive(&mRwSessionsLock);

    ClientList::SessionMap::iterator it = mSessions.find(session->GetHash());

    if ( it != mSessions.end() )
    {
      mSessions.erase(it);

      std::cout << ("Removing stale session...\n");

      session->KillCleanupTimer();
      session->Release();
    }

    ReleaseSRWLockExclusive(&mRwSessionsLock);
  }

  void RemoveAllSessions()
  {
    AcquireSRWLockExclusive(&mRwSessionsLock);

    for (ClientList::SessionMap::iterator it = mSessions.begin(); it != mSessions.end(); ++it)
    {
      std::cout << ("Removing stale session...\n");

      it->second->KillCleanupTimer();
      it->second->Release();
    }

    mSessions.clear();

    ReleaseSRWLockExclusive(&mRwSessionsLock);
  }

  static void CALLBACK CleanupSession(PTP_CALLBACK_INSTANCE instance, PVOID param, PTP_TIMER timer)
  {
    RUdpSocket* session = reinterpret_cast<RUdpSocket*>(param);

    if (session->Age() > SESSION_EXPIRE_PERIOD_MS)
    {
      session->GetSessionList()->RemoveStaleSession(session);
    }
  }

  void CreateCleanupTimer(RUdpSocket* session, PTP_CALLBACK_ENVIRON tpEnv)
  {
    if ( SESSION_EXPIRE_PERIOD_MS > 0 )
    {
      // Periodic thread that cleans up old Sockets
      PTP_TIMER timer = CreateThreadpoolTimer(CleanupSession, session, tpEnv);

      ULARGE_INTEGER ulDueTime;
      FILETIME FileDueTime;

      ulDueTime.QuadPart = (LONGLONG) -(SESSION_EXPIRE_PERIOD_MS * 10 * 1000); // First iteration
      FileDueTime.dwHighDateTime = ulDueTime.HighPart;
      FileDueTime.dwLowDateTime  = ulDueTime.LowPart;

      session->SetCleanupTimer(timer);

      SetThreadpoolTimer(timer, &FileDueTime, SESSION_EXPIRE_PERIOD_MS, 0);
    }
  }

private:
  typedef stdext::hash_map<UINT64, RUdpSocket*> SessionMap;

  SessionMap mSessions;
  SRWLOCK mRwSessionsLock;
};

